package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.common.vo.PageResult;
import com.leyou.dto.*;
import com.leyou.item.client.ItemClient;
import com.leyou.search.domain.Goods;
import com.leyou.search.domain.SearchRequest;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.mapper.GoodsRepository;
import com.leyou.search.utils.HighlightUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.index.Term;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchService {
    @Autowired
    private ItemClient itemClient;
    @Autowired
    private ElasticsearchTemplate template;
    //创建索引
    public Goods invertGoods(SpuDTO spuDTO){
        List<Map<String,Object>> skuMaps = new ArrayList<>();
        List<SkuDTO> skuDTOList = itemClient.findSkuById(spuDTO.getId());
        skuDTOList.forEach(skuDTO -> {
            Map<String,Object> map = new HashMap<>();
            map.put("id",skuDTO.getId());
            map.put("title",skuDTO.getTitle().substring(spuDTO.getName().length()));
            map.put("price",skuDTO.getPrice());
            map.put("image", StringUtils.substringBefore(skuDTO.getImages(),","));
            skuMaps.add(map);
        });
        String skus = JsonUtils.toString(skuMaps);

        Set<Long> price = skuDTOList.stream().map(SkuDTO::getPrice).collect(Collectors.toSet());

        Map<String,Object> specs = new HashMap<>();

        List<SpecParamDTO> specParamDTOS = itemClient.querySpecParams(null, spuDTO.getCid3(), true);
        SpuDetailDTO spuDetailDTO = itemClient.findSpuDetailById(spuDTO.getId());
        Map<Long, Object> genericSpecMap = JsonUtils.toMap(spuDetailDTO.getGenericSpec(), Long.class, Object.class);
        Map<Long, List<Object>> specialSpecMap = JsonUtils.nativeRead(spuDetailDTO.getSpecialSpec(), new TypeReference<Map<Long, List<Object>>>() {});
        specParamDTOS.forEach(specParamDTO -> {
            String key = specParamDTO.getName();
            Object value = null;
            if(specParamDTO.getGeneric()){
                value = genericSpecMap.get(specParamDTO.getId());
            }else {
                value = specialSpecMap.get(specParamDTO.getId());
            }
            if(specParamDTO.getNumeric()){
                value = chooseSegment(value,specParamDTO);
            }
            specs.put(key,value);
        });

        Goods goods = new Goods();
        goods.setId(spuDTO.getId());

        goods.setCategoryId(spuDTO.getCid3());
        goods.setSubTitle(spuDTO.getSubTitle());
        goods.setSpuTitle(spuDTO.getName());
        goods.setCreateTime(spuDTO.getCreateTime().getTime());
        goods.setBrandId(spuDTO.getBrandId());
        goods.setAll(spuDTO.getCategoryName()+spuDTO.getBrandName());
        goods.setSpecs(specs);
        goods.setPrice(price);
        goods.setSkus(skus);
        return goods;
    }

    private String chooseSegment(Object value, SpecParamDTO p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }
    //门户网站分页
    public PageResult<GoodsDTO> findByPage(SearchRequest searchRequest) {
        try {
            NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
            //封装分页条件
            nativeSearchQueryBuilder.withPageable(PageRequest.of(searchRequest.getPage()-1,searchRequest.getSize()));
            //封装需要显示的字段
            nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id","subTitle","skus","spuTitle"},null));
            //封装查询以及匹配的分词字段
            nativeSearchQueryBuilder.withQuery(getFilterBuilder(searchRequest));
            //指定高亮字段
            HighlightUtils.highlightField(nativeSearchQueryBuilder,"spuTitle");
            //查询
            AggregatedPage<Goods> goods = template.queryForPage(nativeSearchQueryBuilder.build(), Goods.class,HighlightUtils.highlightBody(Goods.class,"spuTitle"));
            //分页
            PageResult<GoodsDTO> result = new PageResult<>(goods.getTotalElements(),goods.getTotalPages(),BeanHelper.copyWithCollection(goods.getContent(),GoodsDTO.class));
            return result;
        } catch (Exception e) {
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }
    }

    private QueryBuilder getFilterBuilder(SearchRequest searchRequest) {

        //封装新的参数  过滤参数 键值对形式的
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //取交集  匹配数据
        boolQuery.must(QueryBuilders.multiMatchQuery(searchRequest.getKey(),new String[]{"all","spuTitle"}).operator(Operator.AND));
        //得到前台过滤参数
        Map<String, Object> paramMap = searchRequest.getParamMap();
        //遍历设置 键值对 形式与索引库一致  值已经在前台 传递正确了 已经没有对象了
        paramMap.forEach((s, o) -> {
            if(s.equals("分类")){
                s = "categoryId";
            }else if(s.equals("品牌")){
                s = "brandId";
            }else{
                s = "specs."+s+".keyword";
            }
            //封装过滤参数
            boolQuery.filter(QueryBuilders.termQuery(s,o));
        });
        return boolQuery;
    }

    //查找 索引库显示 brand category
    public Map<String, List<?>> loadFilterParams(SearchRequest searchRequest) {
        //创建一个 集合用来存储  由于页面是有序的 所以使用link
        Map<String, List<?>> map = new LinkedHashMap<>();
        //聚合和分页用的是同一个方法 所以但是参数不能消失 所以必须带上
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        //封装分页条件
        nativeSearchQueryBuilder.withPageable(PageRequest.of(0,1));
        //封装查询以及匹配的分词字段
        nativeSearchQueryBuilder.withQuery(getFilterBuilder(searchRequest));
        //指定分类的聚合名称
        String categoryAgg = "categoryAgg";
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(categoryAgg).field("categoryId"));
        String brandAgg = "brandAgg";
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(brandAgg).field("brandId"));
        //查询
        AggregatedPage<Goods> goods = template.queryForPage(nativeSearchQueryBuilder.build(), Goods.class);
        //得到所有聚合结果
        Aggregations aggregations = goods.getAggregations();
        //得到具体的分类的结果
        Terms categoryTerms = aggregations.get(categoryAgg);
        Terms brandTerms = aggregations.get(brandAgg);
        //处理结果
        List<Long> categoryIds = handlerCategoryAggResult(map,categoryTerms);
        handlerBrandAggResult(map,brandTerms);
        handlerSpecParamFilterMap(map,categoryIds,getFilterBuilder(searchRequest));
        return map;
    }

    //处理参数
    private void handlerSpecParamFilterMap(Map<String, List<?>> map, List<Long> categoryIds, QueryBuilder queryBuilder) {
        categoryIds.forEach(categoryId ->{
            //聚合和分页用的是同一个方法 所以但是参数不能消失 所以必须带上
            NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
            //封装分页条件
            nativeSearchQueryBuilder.withPageable(PageRequest.of(0,1));
            //封装查询以及匹配的分词字段
            nativeSearchQueryBuilder.withQuery(queryBuilder);
            List<SpecParamDTO> list = itemClient.querySpecParams(null, categoryId, true);
            list.forEach(specParamDTO ->{
                //给上查询条件
                String aggName = specParamDTO.getName();
                //规格参数的域名称 注意 要加上所有的信息
                String aggField = "specs."+specParamDTO.getName()+".keyword";
                //加上聚合条件
                nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(aggName).field(aggField));
            });
            //查询
            AggregatedPage<Goods> goods = template.queryForPage(nativeSearchQueryBuilder.build(), Goods.class);

            Aggregations aggregations = goods.getAggregations();
            list.forEach(specParamDTO ->{
                Terms terms = aggregations.get(specParamDTO.getName());
                List<String> stringList = terms.getBuckets().stream().map(Terms.Bucket::getKeyAsString)
                        .collect(Collectors.toList());
                map.put(specParamDTO.getName(),stringList);
            });
        } );
    }

    private void handlerBrandAggResult(Map<String, List<?>> map, Terms brandTerms) {
        List<BrandDTO> list = brandTerms.getBuckets().stream().map(Terms.Bucket::getKeyAsNumber)
                .map(Number::longValue).map(itemClient::queryBrandById)
                .collect(Collectors.toList());
        map.put("品牌",list);
    }

    private List<Long> handlerCategoryAggResult(Map<String, List<?>> map, Terms categoryTerms) {
        //getBuckets 直接拿到了所有的
        List<Long> list = categoryTerms.getBuckets().stream().map(Terms.Bucket::getKeyAsNumber)
                .map(Number::longValue).collect(Collectors.toList());
        List<CategoryDTO> categorys = itemClient.findByCategorysByIds(list);
        map.put("分类",categorys);
        return list;
    }

    @Autowired
    private GoodsRepository goodsRepository;

    public void addSearchIndex(Long spuId) {
        SpuDTO spuDTO = itemClient.findSpuBySpuId(spuId);
        Goods goods = invertGoods(spuDTO);
        //保存 索引到索引库
        goodsRepository.save(goods);
    }
    //删除索引
    public void delSearchIndex(Long spuId) {
        goodsRepository.deleteById(spuId);
    }
}
